Technical Note TN2015
Locating Application Support Files under Mac OS X

目次

アプリケーションを正しく動作させるには、辞書、プラグイン、スクリプトなどの追加ファイルが必要になります。Apple ではそのようなファイルを、「アプリケーションサポート」、「スクリプティング機能追加」、「スクリプト」、「Internet プラグイン」などのフォルダに格納することを推奨します。これらのフォルダは、Mac OS 8、9、および Mac OS X では透過的に動作する FindFolder API を使ってその位置を確認することができますが、これらのファイルを、関連のあるアプリケーションに隣接するフォルダに格納することを好むデベロッパもいます。そのように格納されているファイルを探すには、先にアプリケーションが自身の格納されている場所を知る必要があります。Mac OS X では、これらの作業をするのに過去にデベロッパが利用してきたさまざまな方法を見直す必要があります。

Mac OS X 上で アプリケーションが Classic アプリケーションとして動作している場合には、アプリケーションの位置を把握するのに現在使用されている方法はそのまま機能します。これが Classic アプリケーションによって実現される互換性です。このテクニカルノートをこれ以上読む必要はありません。

アプリケーションを Mac OS X 上で独立のモダンプロセスとして動作させることによって得られる数々の利点を得るためアプリケーションを Carbon 対応にしている場合には、このテクニカルノートを読む必要があります。

 [2001年3月27日]






パッケージの使用

アプリケーションをすでに Carbon 対応にしていてもパッケージとして提供していないなら、そうすることを強く推奨します。パッケージは Mac OS 9.0 以降でサポートされており、ソフトウェアを配布する方法として推奨されています。MetroWerks CodeWarrior によって生成されているものと同じ、CFM ベースのデータフォークとリソースフォークを持つ単一のファイルとしてアプリケーションを構築している段階にあるとしても、Mac OS 9.x と Mac OS X の両方で動作する最小限のパッケージとしてアプリケーションを提供することができます。



folder MyCFMPkgAppl.app
alias      MyCFMPkgAppl alias
folder     Contents
folder         MacOSClassic
appl.              MyCFMPkgAppl


このパッケージ構造を使用すれば、CFBundle API を使ってパッケージやその構成要素の位置を探せるという利点があります。また、最終的に Mach-O コード形式に移行するときにもコードの互換性が確保されている、という利点があります。さらに、Apple の ProjectBuilder によってパッケージ構造でのみ配布される Mac OS X 上で動作することの利点をすべて享受できます。



folder MyMachOPkgAppl.app
alias      MyMachOPkgAppl alias
folder     Contents
folder         MacOS
appl.              MyMachOPkgAppl
folder         Resources
file               MyMachOPkgAppl.rsrc
…                   …
…               …




注:
パッケージの詳細についてはTN1188「Packages in Mac OS 9」 を参照してください。



今パッケージ構造を使用しなければ、将来同じ Carbon API を使うとしても、CFM から Mach-O に切り換えるときに再度コードを変えなければなりません。

CFBundle API

実行するアプリケーションの位置を特定するのにデベロッパがよく使用している方法が3つあります。それらに代わる同等のモダン CFBundle API が3つあり、さらに Mac OS X のみで利用可能な Process Manager API が1つあります。

以下に示すのが3つの CFBundle API です。

OSErr GetApplicationPackageFSSpecFromBundle( FSSpecPtr theFSSpecPtr )
{
    OSErr err = fnfErr;

    CFBundleRef myAppsBundle = CFBundleGetMainBundle();
    if( myAppsBundle == NULL )
        return err;

    CFURLRef myBundleURL = CFBundleCopyBundleURL( myAppsBundle );
    if( myBundleURL == NULL )
        return err;

    FSRef myBundleRef;

    Boolean ok = CFURLGetFSRef( myBundleURL, &myBundleRef );

    CFRelease( myBundleURL );
    if( !ok )
        return err;

    return FSGetCatalogInfo( &myBundleRef, kFSCatInfoNone,
                             NULL, NULL, theFSSpecPtr, NULL);
}

OSErr GetApplicationResourceFSSpecFromBundle( FSSpecPtr theFSSpecPtr )
{
    OSErr err = fnfErr;

    CFBundleRef myAppsBundle = CFBundleGetMainBundle();
    if( myAppsBundle == NULL )
        return err;

    CFURLRef myBundleURL = CFBundleCopyResourcesDirectoryURL( myAppsBundle );
    if( myBundleURL == NULL )
        return err;

    FSRef myBundleRef;

    Boolean ok = CFURLGetFSRef( myBundleURL, &myBundleRef );

    CFRelease( myBundleURL );
    if( !ok )
        return err;

    return FSGetCatalogInfo( &myBundleRef, kFSCatInfoNone,
                             NULL, NULL, theFSSpecPtr, NULL);
}

OSErr GetExecutableParentFSSpecFromBundle( FSSpecPtr theFSSpecPtr )
{
    OSErr err = fnfErr;

    CFBundleRef myAppsBundle = CFBundleGetMainBundle();
    if( myAppsBundle == NULL )
        return err;

    CFURLRef myBundleURL = CFBundleCopyExecutableURL( myAppsBundle );
    if( myBundleURL == NULL )
        return err;

    FSRef myBundleRef;

    Boolean ok = CFURLGetFSRef( myBundleURL, &myBundleRef );

    CFRelease( myBundleURL );
    if( !ok )
        return err;

    return FSGetCatalogInfo( &myBundleRef, kFSCatInfoNone,
                             NULL, NULL, theFSSpecPtr, NULL);
}

リスト1 - CFBundle API



Mac OS X のみで利用可能な Process Manager の追加 API

OSErr GetApplicationBundleFSSpec( FSSpecPtr theFSSpecPtr )
{
    OSErr err;
    ProcessSerialNumber psn;

    err = GetCurrentProcess(&psn);
    if( err != noErr )
        return err;

    FSRef location;

    err = GetProcessBundleLocation(&psn, &location);
    if( err != noErr )
        return err;

    return FSGetCatalogInfo( &location, kFSCatInfoNone,
                             NULL, NULL, theFSSpecPtr, NULL);
}

リスト2 - Process Manager API



従来の方法

下記はこれまでデベロッパが使っていた3つの従来の方法です。

OSErr GetApplicationPackageFSSpec( FSSpecPtr theFSSpecPtr )
{
    OSErr               err;
    Str255              applName;
    ProcessSerialNumber psn;
    ProcessInfoRec      info;

    info.processInfoLength = sizeof(ProcessInfoRec);
    info.processName = applName;
    info.processAppSpec = theFSSpecPtr;
    err = GetCurrentProcess( &psn );
    if( err != noErr )
        return err;

    err = GetProcessInformation( &psn, &info );
    return err;
}

OSErr GetApplicationResourceFSSpec( OSType creator,
                                    FSSpecPtr theFSSpecPtr)
{
    Handle   creatorHandle;
    OSErr    err;
    Str255   applName;
    FCBPBRec theFCBPBRec;

    // creator は必ず一意であり、また必ず
    // アプリケーションのリソースフォーク
    // のみに対応する、4バイトのクリエータコードである

    creatorHandle = GetResource( creator, 0 );
    if( creatorHandle == NULL )
        return resNotFound;

    theFCBPBRec.ioCompletion = nil;
    theFCBPBRec.ioNamePtr = applName;
    theFCBPBRec.ioRefNum = HomeResFile( creatorHandle );
    theFCBPBRec.ioFCBIndx = 0;
    err = PBGetFCBInfoSync( &theFCBPBRec );

    if( err != noErr )
        return err;

    err = FSMakeFSSpec( theFCBPBRec.ioFCBVRefNum,
                        theFCBPBRec.ioFCBParID, applName, theFSSpecPtr );
    return err;
}

OSErr GetExecutableParentFSSpec( OSType creator, FSSpecPtr theFSSpecPtr )
{
    Handle creatorHandle;
    OSErr err;
    Str255 volName;
    short vRefNum;
    long dirID;

    // creator は必ず一意であり、また必ず
    // アプリケーションリソースフォーク
    // のみに対応する、4バイトのクリエータコードである

    creatorHandle = GetResource( creator, 0 );

    if( creatorHandle == NULL )
        return resNotFound;

    err = HGetVol( volName, &vRefNum, &dirID );
    if( err != noErr )
        return err;

    // HGetVol によって返される vRefNum は実際は
    // ボリュームの Reference Number ではない。
    // そのため、実行可能なものとリソースが
    // 同じボリューム上にあると想定する、以下の
    // 追加コールが必要になる。

    err = GetVRefNum( HomeResFile( creatorHandle ), &vRefNum );
    if( err != noErr )
        return err;

    err = FSMakeFSSpec( vRefNum, dirID, nil, theFSSpecPtr );
    return err;
}

リスト3 - 従来の方法



文書化されていない File Manager ルーチンの副作用を使った(つまり、信頼性がないということ)4つ目の方法についても念のため記しておきましょう。アプリケーションを起動してすぐに FSMakerFSSpec(0,0,nil,theFSSpecPtr)を呼び出すと FSSpec はデフォルトのボリュームのデフォルトのディレクトリの、そしてアプリケーションが格納されているフォルダで埋められています。

結果

パッケージ化と CFBundle API の利点を示すために、以下のすべてのケースにおいてそれぞれの方法が何を返すかを見てみましょう。

ケース 1: Mac OS 9 上で実行されるファイルとして配布される Carbon CFM アプリケーション

ケース 2: Mac OS X 上で実行されるファイルとして配布される Carbon CFM アプリケーション


ケース 3: Mac OS 9 上で実行されるパッケージとして配布される Carbon CFM アプリケーション

ケース 4: Mac OS X 上で実行されるパッケージとして配布される Carbon CFM アプリケーション


最終的な目標:

ケース 5: Mac OS X 上で実行されるパッケージとして配布される Carbon Mach-O アプリケーション


ここで CFBundle による方法 1.a、1.b 、1.c、Process Manager による方法 2、そして従来の方法 3a、3b、3cを呼び出します。

FSSpec が指す対象を以下の表に示します。



 
1.a
1.b
1.c
2
3.a
3.b
3.c
ケース 1
アプリケーションフォルダ
N/A
アプリケーションファイル
N/A
アプリケーションファイル
アプリケーションファイル
アプリケーションフォルダ
ケース 2
アプリケーションフォルダ
アプリケーションフォルダ
アプリケーションファイル
N/A
アプリケーションファイル
アプリケーションファイル
アプリケーションフォルダ
ケース 3
.app「フォルダ」
N/A
アプリケーションバイナリ
N/A
アプリケーションバイナリ
アプリケーションバイナリ
MacOSClassic
ケース 4
.app「フォルダ」
N/A
アプリケーションバイナリ
N/A
アプリケーションバイナリ
アプリケーションバイナリ
MacOSClassic
ケース 5
.app「folder」
Resources
アプリケーションバイナリ
.app「フォルダ」
アプリケーションバイナリ
rsrc ファイル
MacOS

アプリケーションがパッケージとして配布された場合には、アプリケーションバイナリ(対象コードを含むファイル)の位置を探してもあまり意味がありません。というのも、パッケージの構造が決して変わらないと考えるのは危険だからです。このため、アプリケーションをユーザーの見たままの(アイコン、つまり.app「フォルダ」のように)位置で探す唯一の安全な方法は、方法の1.aあるいは2です。

現在の配布上の制約によっては(例えばターゲットとしているプラットフォームがどれかによっては)どの方法が最適かを判断しなければなりませんが、上記の表を見ればわかるように、ケース5 へのルートを作るには、まだ CFM フォーマットを一時的に使用していたとしても、パッケージ構造を採用して、CFBundle API を使うのが最良の方法です。


先頭に戻る

参考文献

テクニカルノートTN1188「Packages in Mac OS 9」


先頭に戻る


Technical Notes by Date | Number | Technology | Title
Developer Documentation | Technical Q&As | Development Kits | Sample Code